import os
import re
from pathlib import Path
from loguru import logger
from collections import defaultdict
from dataclasses import dataclass, field

from typing import List, Dict, Optional, Set, Tuple, Union
from utils.loader import FileProcessor
from utils.scanner import FileScanner


@dataclass
class MediaItem:
    """基于NFO锚点的媒体资源集合"""

    nfo: Path
    file: str = None  # nfo文件名称
    video: Optional[Path] = None
    poster: Optional[Path] = None
    thumb: Optional[Path] = None
    fanarts: Optional[Path] = None
    subtitles: List[Path] = field(default_factory=list)
    extra_fanarts: List[Path] = field(default_factory=list)

    @property
    def base_name(self) -> str:
        """获取基础名称（NFO文件名不含扩展名）"""
        return self.nfo.stem

    @property
    def directory(self) -> Path:
        """获取文件所在目录"""
        return self.nfo.parent

    def __repr__(self) -> str:
        """自定义对象表示形式"""
        return f"<MediaItem {self.base_name}>"

    def to_dict(self) -> Dict[str, any]:
        """转换为字典格式"""
        return {
            "file": self.base_name,
            "nfo": str(self.nfo),
            "video": str(self.video) if self.video else None,
            "poster": str(self.poster) if self.poster else None,
            "thumb": str(self.thumb) if self.thumb else None,
            "fanarts": str(self.fanarts) if self.fanarts else None,
            "subtitles": [str(p) for p in self.subtitles],
            "extra_fanarts": [str(p) for p in self.extra_fanarts]
        }


@dataclass
class ActorAvatarItem:
    name: str
    path: Path


class MediaScanner:
    """媒体资源扫描器，收集媒体文件的存放路径，一个视频文件一个MediaItem对象"""

    # 支持的视频扩展名
    __VIDEO_EXTS = {'.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv', '.m4v'}
    # 支持的图片扩展名
    __IMAGE_EXTS = {'.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp'}
    # 支持的字幕扩展名
    __SUBTITLE_EXTS = {'.srt', '.ass', '.ssa', '.sub', '.vtt'}
    # 资源类型后缀映射
    __RESOURCE_TYPES = {
        'poster': ['-poster', '_poster', '.poster', '海报'],
        'thumb': ['-thumb', '_thumb', '.thumb', '缩略图', 'thumbnail'],
        'fanarts': ['-fanart', '_fanart', '.fanart', '背景图'],
    }
    # CD标识模式（用于识别多CD视频）
    __CD_PATTERNS = [  # 欠缺_的情况
        r'[-_]cd(\d+)',
        r'[-_]part(\d+)',
        r'[-_]pt(\d+)',
        r'[-_]d(\d+)',
        r'[-_]disk(\d+)',
        r'[-_](\d+)of(\d+)',
        r'[ .]cd(\d+)[ .]',
    ]

    def __init__(self, root: Union[str, Path]):
        self._root = root
        self._sca = FileScanner()

    def scan_media_directory(self, max_depth: int = 3, recursive: bool = False) -> List[MediaItem]:
        """
        扫描目录结构并返回所有NFO文件对应的媒体资源集合
        :param max_depth: 最大递归深度
        :param recursive: 是否递归扫描子目录
        :return: MediaItem对象列表
        """
        media_path_mate = []
        # 遍历目录
        for current_dir, _, files in os.walk(self._root):
            current_path = Path(current_dir)
            depth = len(current_path.relative_to(self._root).parts)

            # 检查深度限制
            if recursive and depth > max_depth:
                continue

            # 查找所有NFO文件
            nfo_files = [f for f in files if Path(f).suffix.lower() == '.nfo']

            if not nfo_files:
                continue
            logger.info(f"发现NFO文件目录: {current_dir} ({len(nfo_files)}个NFO文件)")

            # 预创建所有文件的Path对象
            all_files = [current_path / f for f in files]

            # 按扩展名分组文件，优化后续匹配
            file_cache = defaultdict(list)
            for file_path in all_files:
                suffix = file_path.suffix.lower()
                file_cache[suffix].append(file_path)
            logger.debug('File cache>>> {}', file_cache)

            # 为每个NFO文件创建媒体项
            for nfo_file in nfo_files:
                nfo_path = current_path / nfo_file
                media_item = MediaItem(nfo=nfo_path, file=nfo_path.stem)
                media_item = self._match_resources(media_item, current_path, file_cache)
                media_path_mate.append(media_item)
        else:
            self._print_summary(media_path_mate)
        return media_path_mate

    @classmethod
    def _match_resources(
            cls,
            media_item: MediaItem,
            directory: Path,
            file_cache: Dict[str, List[Path]]
    ) -> MediaItem:
        """
        为单个媒体项匹配资源
        :param media_item: 基础媒体项（包含NFO文件）
        :param directory: 目录路径
        :param file_cache: 按扩展名分组的文件缓存
        :return: 更新后的媒体项
        """
        base_name = media_item.base_name

        # 匹配视频文件
        video_files = []
        for ext in cls.__VIDEO_EXTS:
            video_files.extend(file_cache.get(ext, []))

        if video_files:
            matching_videos = cls._find_matching_files(base_name, video_files, cls.__VIDEO_EXTS)
            if matching_videos:
                media_item.video = cls._select_best_video(matching_videos)

        # 匹配图片资源（海报、缩略图、背景图）
        image_files = []
        for ext in cls.__IMAGE_EXTS:
            image_files.extend(file_cache.get(ext, []))

        if image_files:
            # 匹配海报
            poster_files = cls._find_matching_files(base_name, image_files, cls.__IMAGE_EXTS,
                                                    cls.__RESOURCE_TYPES['poster'])
            if poster_files:
                media_item.poster = poster_files[0]

            # 匹配缩略图
            thumb_files = cls._find_matching_files(base_name, image_files, cls.__IMAGE_EXTS,
                                                   cls.__RESOURCE_TYPES['thumb'])
            if thumb_files:
                media_item.thumb = thumb_files[0]

            # 匹配背景图
            fanarts_files = cls._find_matching_files(base_name, image_files, cls.__IMAGE_EXTS,
                                                     cls.__RESOURCE_TYPES['fanarts'])
            if fanarts_files:
                media_item.fanarts = fanarts_files[0]

        # 字幕匹配 - 使用专用方法
        subtitle_files = []
        for ext in cls.__SUBTITLE_EXTS:
            subtitle_files.extend(file_cache.get(ext, []))

        if subtitle_files:
            # 调用专用字幕匹配方法
            media_item.subtitles = cls._match_subtitles(base_name, subtitle_files)
            logger.info(f"匹配字幕: {base_name} -> {len(media_item.subtitles)}个字幕文件")

        # 扫描额外剧照
        media_item = cls._match_extra_fanarts(media_item, directory)

        return media_item

    @classmethod
    def _find_matching_files(
            cls,
            base_name: str,
            candidate_files: List[Path],
            extensions: Set[str],
            keywords: Optional[List[str]] = None
    ) -> List[Path]:
        """
        查找匹配基础名的文件
        :param base_name: 基础名称
        :param candidate_files: 候选文件列表
        :param extensions: 允许的文件扩展名
        :param keywords: 可选的关键词列表（用于进一步筛选）
        :return: 匹配的文件列表
        """
        matching_files = []

        for file_path in candidate_files:
            # 确保文件扩展名匹配
            if file_path.suffix.lower() not in extensions:
                continue

            # 检查文件名是否以基础名开头
            if file_path.stem.startswith(base_name):
                # 检查关键词（如果有）
                if keywords:
                    stem_suffix = file_path.stem[len(base_name):].lower()
                    if any(kw.lower() in stem_suffix for kw in keywords):
                        matching_files.append(file_path)
                else:
                    matching_files.append(file_path)

        # 按文件名排序（确保一致性）
        return sorted(matching_files, key=lambda f: f.name)

    @classmethod
    def _select_best_video(cls, video_files: List[Path]) -> Path:
        """
        从多个视频文件中选择最佳的一个
        :param video_files: 视频文件列表
        :return: 最佳视频文件路径
        """
        # 如果只有一个视频文件，直接返回
        if len(video_files) == 1:
            return video_files[0]

        # 优先选择CD1（如果有）
        for video in video_files:
            cd_match = cls._find_cd_number(video.stem)
            if cd_match and cd_match[0] == 1:
                return video

        # 选择文件大小最大的
        return max(video_files, key=lambda f: f.stat().st_size)

    @classmethod
    def _find_cd_number(cls, file_name: str) -> Optional[Tuple[int, Optional[int]]]:
        """
        从文件名中提取CD编号信息
        :param file_name: 文件名（不含扩展名）
        :return: (CD编号, 总CD数) 或 None
        """
        file_name = file_name.lower()

        for pattern in cls.__CD_PATTERNS:
            match = re.search(pattern, file_name)
            if match:
                try:
                    # 处理如 "cd1" 的情况
                    if match.lastindex == 1:
                        cd_num = int(match.group(1))
                        return cd_num, None
                    # 处理如 "1of2" 的情况
                    elif match.lastindex == 2:
                        cd_num = int(match.group(1))
                        total_cds = int(match.group(2))
                        return cd_num, total_cds
                except (ValueError, IndexError):
                    continue

        return None

    def _print_summary(self, media_path_mate: List[: MediaItem]):
        """打印媒体资源摘要"""
        print(f"\n{'=' * 60}")
        print(f"扫描完成: 共发现 {len(media_path_mate)} 个媒体项目; 目录: {self._root}")
        print(f"{'=' * 60}")

        # 按目录分组
        items_by_dir = defaultdict(list)
        for item in media_path_mate:
            items_by_dir[item.directory].append(item)

        # 打印每个目录的摘要
        for directory, items in items_by_dir.items():
            print(f"\n目录: {directory}")
            print(f"媒体项目数: {len(items)}")

            for i, item in enumerate(items, 1):
                print(f"\n项目 #{i}: {item.base_name}")

                if item.video:
                    cd_match = self._find_cd_number(item.video.stem)
                    cd_info = f" (CD{cd_match[0]})" if cd_match else ""
                    print(f"  视频: {item.video.name}{cd_info}")

                if item.poster:
                    print(f"  海报: {item.poster.name}")

                if item.thumb:
                    print(f"  缩略图: {item.thumb.name}")

                if item.fanarts:
                    print(f"  背景图: {item.fanarts.name}")

                if item.subtitles:
                    print(f"  字幕文件: {len(item.subtitles)}个")
                    for sub in item.subtitles:
                        print(f"    - {sub.name}")

                if item.extra_fanarts:
                    print(f"  额外剧照: {len(item.extra_fanarts)}张")

            print("-" * 60)

    @classmethod
    def _match_extra_fanarts(
            cls,
            media_item: MediaItem,
            directory: Path
    ) -> MediaItem:
        """
        匹配额外剧照 - 精确目录匹配方案
        :param media_item: 媒体项
        :param directory: 目录路径
        :return: 更新后的媒体项
        """
        # 只处理名为"extrafanart"的目录
        extra_dir = directory / "extrafanart"

        if extra_dir.exists() and extra_dir.is_dir():
            logger.info(f"发现剧照目录: {extra_dir}")

            # 使用glob高效获取图片文件
            fanarts = list(extra_dir.glob('*.*'))
            media_item.extra_fanarts = [
                f for f in fanarts
                if f.is_file() and f.suffix.lower() in cls.__IMAGE_EXTS
            ]

            logger.info(f"添加剧照: 共{len(media_item.extra_fanarts)}张")
        else:
            logger.info(f"未发现剧照目录: {extra_dir}")

        return media_item

    @classmethod
    def _match_subtitles(
            cls,
            base_name: str,
            candidate_files: List[Path]
    ) -> List[Path]:
        """
        精确匹配与NFO文件同名的字幕文件
        :param base_name: NFO文件的基础名称（不含扩展名）
        :param candidate_files: 候选字幕文件列表
        :return: 匹配的字幕文件列表
        """
        # 精确匹配：文件名完全等于base_name的字幕
        exact_matches = [
            f for f in candidate_files
            if f.stem == base_name
        ]

        # 兼容匹配：Windows系统大小写不敏感的特殊处理
        if os.name == 'nt':  # Windows系统
            base_lower = base_name.lower()
            case_insensitive_matches = [
                f for f in candidate_files
                if f.stem.lower() == base_lower
            ]
            # 合并结果并去重
            all_matches = exact_matches + [
                f for f in case_insensitive_matches
                if f not in exact_matches
            ]
            return all_matches

        return exact_matches

    def scan_avatar_images(self):
        """扫描指定目录下的头像图片，返回 [{name:path}]结构数据"""
        logger.debug('开始扫描目录>>> {}', self._root)
        return self._sca.scan_directory(self._root, self.__IMAGE_EXTS, recursive=False, is_path=True)

    def find_image(
            self, value: str, avatar_img_dict, fuzzy_matching=False, threshold=90
    ) -> Optional[Path]:
        """
        根据演员名字查找对应的图片文件
        :param value 演员姓名
        :param avatar_img_dict 演员数据
        :param fuzzy_matching 是否开启模糊匹配
        :param threshold 模糊匹配时设置的阈值
        :return 匹配的图片文件路径
        """
        return self._sca.file_filter(value, avatar_img_dict, simple=fuzzy_matching, threshold=threshold)

    @classmethod
    def export_to_json(cls, media_data_mate: List[MediaItem], output_file: Path = None):
        """将扫描结果导出为JSON文件"""
        data = {
            "count": len(media_data_mate),
            "items": {item.nfo.stem: item.to_dict() for item in media_data_mate}
        }
        output_file = output_file or Path.cwd() / "media_scan_result.json"
        FileProcessor(output_file, model='w').save(data)
        logger.info(f"结果已导出到: {output_file}")


if __name__ == "__main__":
    # 示例根目录
    root_ = Path(r"W:\Video\亚洲\有码")

    # 扫描目录
    media = MediaScanner(root_)
    # 导出文件
    media_data_json = media.scan_media_directory()
    media.export_to_json(media_data_json)
